package edu.mercer.sse.tests.math;

import static org.junit.Assert.*;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.junit.Test;
import org.junit.After;

import com.google.common.collect.Lists;

import edu.mercer.sse.tasks.math.MonteCarloPi;
import edu.mercer.sse.tasks.math.PiAccuracyFunction;

/**
 * 
 * Test Monte Carlo Pi calculation for single threaded environment.<p>
 *
 * Run multiple Monte Carlo Pi tasks, each a separate Callable object 
 * 	instance. Ensure Pi cumulative results obtained from successive executions
 * 	through use of thread-globals.<p>
 *
 */

public class TestSingleThread
{
	/** Single thread executor: solo-thread environment */
	
	private final ExecutorService _threadEx = Executors
			.newSingleThreadExecutor();

	/** Number of tasks to create and execute */
	
	private static final Integer NUMBER_TASKS = 100; 
	
	/** Number of Monte Carlo guesses, attempts to run for each task */
	
	private static final Double MONTE_CARLO_GUESSES = 10000d; 
	
	/**
	 * Test single thread execution of Monte Carlo Pi estimation.<p>
	 * 
	 * Ensure Pi accuracy increases as more tasks are executed. Tasks are 
	 * 	independent Callable objects, but are using thread-globals from 
	 * 	thread-specific storage proxy object(s).<p>
	 * 
	 * Intermediate values used in Pi calculation are cumulative within the
	 * 	single thread, increasing Pi accuracy with successive task 
	 * 	executions.<p>
	 * 
	 * @throws InterruptedException - thread interrupted
	 * 
	 * @throws ExecutionException - any NumberFormat, division or other errors 
	 * 	wrapped 
	 */
	
	@Test
	public void test() throws InterruptedException, ExecutionException
	{
		assertTrue( "no number of tasks", 
				
				NUMBER_TASKS > 0 );
		
		assertTrue( "no monte carlo number of guesses", 
				
				MONTE_CARLO_GUESSES > 0d );
		
		final List<MonteCarloPi> tasks = Lists.newArrayList();
		
		for( Integer ct=0; ct < NUMBER_TASKS; ct++ )
		{
			tasks.add( new MonteCarloPi( "singlethreadtest", 
										 MONTE_CARLO_GUESSES ));
			
		}//for
		
		final List<Future<Double>> results = _threadEx.invokeAll( tasks );

		Boolean stillRunning = false;
		
		while( true )
		{
			for( Integer ct=0; ct < NUMBER_TASKS; ct++ )
			{
				if( ! results.get(ct).isDone() )
				{
					stillRunning = true;
					break;
					
				}//if
			
			}//for
			
			if( ! stillRunning ) break;
			
			Thread.sleep(1000l);
			
		}//while
		
		for( Integer ct=0; ct < NUMBER_TASKS; ct++ )
		{
			Double piEstimate = results.get(ct).get();
			
			DecimalFormat numFormatter = (DecimalFormat) DecimalFormat
					.getInstance();
			
			numFormatter.applyPattern ("#0.000000#");
			
			
			System.out.println( "Task "   
					
					+ ct
					
					+ " Accuracy " + numFormatter.format( 
							new PiAccuracyFunction().apply( piEstimate ) ) 
					
					+ " Pi " + numFormatter.format( piEstimate ));	
			
		}//for

		if( NUMBER_TASKS > 20 )
		{
			Integer NUMBER_TASKS_HALVED = NUMBER_TASKS/2 -1;
			
				if(	new PiAccuracyFunction().apply( results.get(0).get() ) 
						> 
					new PiAccuracyFunction().apply( results.get( 
						NUMBER_TASKS_HALVED ).get() ))
				{
					System.err.println("Monte Carlo accuracy did not improve"
							+ " from 1st task"
							+ " execution to half-way mark");
					
				}//if
		
		}//if
		
		assertTrue( "Monte Carlo accuracy did not improve from 1st task "
				+ "execution to " + NUMBER_TASKS + " task", 
				
				new PiAccuracyFunction().apply( results.get(0).get() ) 
				< 
				new PiAccuracyFunction().apply( results.get(NUMBER_TASKS-1)
						.get() ));

		
		ArrayList<Float> tasksAccuracy = Lists.newArrayList();
		
		Float tasksInaccuracyTotal = 0f;
		
		for( Integer ct=0; ct < NUMBER_TASKS; ct++ )
		{
			Double piEstimate = results.get(ct).get();

			DecimalFormat numFormatter = (DecimalFormat) DecimalFormat
					.getInstance();

			numFormatter.applyPattern ("#0.000000#");


			System.out.println( "Task "   

					+ ct

					+ " Accuracy " + numFormatter.format( 
							new PiAccuracyFunction().apply( piEstimate ) ) 

							+ " Pi " + numFormatter.format( piEstimate ));	

			tasksAccuracy.add( new Float( piEstimate ) );

			tasksInaccuracyTotal += ( 100f - new Float( 
					new PiAccuracyFunction().apply(piEstimate) ) );

		}//for

		tasksInaccuracyTotal = tasksInaccuracyTotal / NUMBER_TASKS;

		System.out.println( "Sum of inaccuracy for " + NUMBER_TASKS 
				+ " tasks : " 
				+ tasksInaccuracyTotal );		
		
		assertTrue( "cumulative inaccuracy higher than expected", 
				
				tasksInaccuracyTotal < 0.1f );
		
	}//method

	
	/**
	 * Shutdown executor service.<p>
	 */
	
	@After
	public void cleanUp()
	{
		_threadEx.shutdown();
		
	}//method
	
	
}//class
